package ali

import (
	"bytes"
	"crypto/hmac"
	"crypto/md5"  // #nosec
	"crypto/sha1" // #nosec
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"time"
)

/**
* Author: dinglin
* Date: 2021/8/17 2:54 下午
* https://help.aliyun.com/document_detail/70292.html?spm=a2c4g.11186623.6.666.6e563af1f9khsi
 */

//nolint

const (
	method       = "POST"
	newline      = "\n"
	MIME         = "application/json"
	ResultBlock  = "block"
	ResultPass   = "pass"
	ResultReview = "review"
)

/*
sceen:
	porn：图片智能鉴黄
	terrorism：图片暴恐涉政
	ad：图文违规
	qrcode：图片二维码
	live：图片不良场景
	logo：图片logo
*/

//scenes = []string{"porn", "terrorism"}

type ImageScanReq struct {
	BizType string          `json:"bizType,omitempty"`
	Scenes  []string        `json:"scenes"`
	Tasks   []ImageScanTask `json:"tasks"`
}

type ImageScanTask struct {
	DataID    string `json:"dataId"`
	URL       string `json:"url"`
	MaxFrames int32  `json:"maxFrames"`
	Interval  int32  `json:"interval"`
}

type ImageScanRsp struct {
	Code      int               `json:"code"`
	Msg       string            `json:"msg"`
	RequestId string            `json:"requestId"` //nolint
	Data      []ImageScanResult `json:"data"`
}

type ImageScanResult struct {
	Code    int           `json:"code"`
	DataId  string        `json:"dataId"` //nolint
	Msg     string        `json:"msg"`
	Results []SceneResult `json:"results"`
	TaskId  string        `json:"taskId"` //nolint
	URL     string        `json:"url"`
}

type SceneResult struct {
	Scene      string  `json:"scene"`
	Rate       float64 `json:"rate"`
	Label      string  `json:"label"`
	Suggestion string  `json:"suggestion"`
}

type ClientInfo struct {
}

/*
aliServerUrl: https://green.cn-beijing.aliyuncs.com/green/image/scan
*/

type ImageScanner struct {
	AliServerURL    string
	BizType         string
	AccessKeyID     string
	AccessKeySecret string
	Scenes          []string
}

func (scanner *ImageScanner) ImageScan(uniqueID, imageURL string) (string, error) {
	body := &ImageScanReq{
		BizType: scanner.BizType,
		Scenes:  scanner.Scenes,
		Tasks: []ImageScanTask{
			{DataID: uniqueID, URL: imageURL, MaxFrames: 100, Interval: 2},
		},
	}

	bodyJSON, _ := json.Marshal(body)
	md5Sum := md5.New() //#nosec
	_, _ = md5Sum.Write(bodyJSON)
	bodyMd5Base64 := base64.StdEncoding.EncodeToString(md5Sum.Sum(nil))

	clientInfo := &ClientInfo{}
	clientJSON, _ := json.Marshal(clientInfo)

	u, _ := url.Parse(scanner.AliServerURL)
	q := u.Query()
	q.Set("clientInfo", string(clientJSON)) //这东西不是必须
	u.RawQuery = q.Encode()

	req, err := http.NewRequest(method, u.String(), bytes.NewReader(bodyJSON))
	if err != nil {
		return "", err
	}
	MakeReqHeader(req, uniqueID, bodyMd5Base64, scanner.AccessKeyID, scanner.AccessKeySecret, u.Path, clientJSON)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var rsp ImageScanRsp
	err = json.Unmarshal(data, &rsp)
	if err != nil {
		return "", err
	}

	if rsp.Code != http.StatusOK {
		return "", errors.New("rsp.code != status_ok")
	}

	if len(rsp.Data) == 0 {
		return "", errors.New("result empty")
	}

	if rsp.Data[0].Code != http.StatusOK {
		return "", errors.New("data[0].code != status_ok")
	}

	var (
		result      = ResultPass
		reviewScene = make([]string, 0, 2)
	)
loop:
	for _, r := range rsp.Data[0].Results {
		switch r.Suggestion {
		case ResultPass:
			continue
		case ResultReview:
			result = ResultReview
			reviewScene = append(reviewScene, r.Scene)
		case ResultBlock:
			result = ResultBlock
			break loop
		}
	}
	return result, nil
}

func signature(acsHeaderKey, acsHeaderValue []string, contentMd5 string, clientInfo []byte, path string, accessKeySecret string, gmtDate string) string {
	b := bytes.Buffer{}

	b.WriteString(method)
	b.WriteString(newline)

	b.WriteString(MIME)
	b.WriteString(newline)

	b.WriteString(contentMd5)
	b.WriteString(newline)

	b.WriteString(MIME)
	b.WriteString(newline)

	b.WriteString(gmtDate)
	b.WriteString(newline)

	for i, k := range acsHeaderKey {
		b.WriteString(k)
		b.WriteString(":")
		b.WriteString(acsHeaderValue[i])
		b.WriteString(newline)
	}

	b.WriteString(path)
	b.WriteString("?clientInfo=")
	b.Write(clientInfo)

	mac := hmac.New(sha1.New, []byte(accessKeySecret))
	_, _ = mac.Write(b.Bytes())
	return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

func MakeReqHeader(req *http.Request, uniqueID, bodyMd5Base64 string, accessKeyID, accessKeySecret string,
	path string, clientJSON []byte) {
	acsHeaderKey := []string{"x-acs-signature-method", "x-acs-signature-nonce", "x-acs-signature-version", "x-acs-version"}
	acsHeaderValue := []string{"HMAC-SHA1", uniqueID, "1.0", "2017-01-12"}
	nowDate := time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")

	req.Header.Set("Accept", MIME)
	req.Header.Set("Content-Type", MIME)
	req.Header.Set("Content-Md5", bodyMd5Base64)
	req.Header.Set("Date", nowDate)
	req.Header.Set(acsHeaderKey[0], acsHeaderValue[0])
	req.Header.Set(acsHeaderKey[1], acsHeaderValue[1])
	req.Header.Set(acsHeaderKey[2], acsHeaderValue[2])
	req.Header.Set(acsHeaderKey[3], acsHeaderValue[3])
	sign := signature(acsHeaderKey, acsHeaderValue, bodyMd5Base64, clientJSON, path, accessKeySecret, nowDate)
	req.Header.Set("Authorization", "acs"+" "+accessKeyID+":"+sign)
}

func uuid() string {
	return fmt.Sprintf("%d-%d", time.Now().UTC().UnixNano(), rand.Int63())
}
